////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                                                            //
//                                                         WNProject                                                          //
//                                                                                                                            //
//            This file is distributed under the BSD 2-Clause open source license. See License.txt for details.               //
//                                                                                                                            //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#ifndef __WN_CRITICAL_SECTION_INL__
#define __WN_CRITICAL_SECTION_INL__

#include "WNCore/inc/WNBase.h"
#include "WNCore/inc/WNAssert.h"

#ifdef _WN_WINDOWS
    #include <windows.h>
#endif

#include "WNConcurrency/inc/WNThread.h"
#include "WNConcurrency/inc/WNInterlocked.h"

__WN_INLINE WN_VOID WNCriticalSectionInitialize(WN_CRITICAL_SECTION_T* _criticalSection) {
    WN_RELEASE_ASSERT(_criticalSection != WN_NULL);

    #ifdef _WN_WINDOWS
        InitializeCriticalSection(_criticalSection);
    #elif defined _WN_LINUX
        _criticalSection->mCounter = 0;
        _criticalSection->mOwner = 0;
        _criticalSection->mRecursion = 0;

        WNSemaphoreInitialize(&(_criticalSection->mSemaphore));
    #endif
}

__WN_INLINE WN_VOID WNCriticalSectionDestroy(WN_CRITICAL_SECTION_T* _criticalSection) {
    WN_RELEASE_ASSERT(_criticalSection != WN_NULL);

    #ifdef _WN_WINDOWS
        DeleteCriticalSection(_criticalSection);
    #elif defined _WN_LINUX
        WNSemaphoreDestroy(&(_criticalSection->mSemaphore));
    #endif
}

__WN_INLINE WN_BOOL WNCriticalSectionTryEnter(WN_CRITICAL_SECTION_T* _criticalSection) {
    #ifdef _WN_LINUX
        const WN_THREAD_ID_T thread = WNThreadID();
    #endif

    WN_RELEASE_ASSERT(_criticalSection != WN_NULL);

    #ifdef _WN_WINDOWS
        return(TryEnterCriticalSection(_criticalSection) != 0 ? WN_TRUE : WN_FALSE);
    #elif defined _WN_LINUX
        if (_criticalSection->mOwner == thread) {
            WNInterlockedIncrement(&(_criticalSection->mCounter));
        } else {
            const WN_ATOM_T result = WNInterlockedCompareSwap(&(_criticalSection->mCounter), 1, 0);

            if (result != 0) {
                return(WN_FALSE);
            }

            _criticalSection->mOwner = thread;
        }

        _criticalSection->mRecursion++;

        return(WN_TRUE);
    #endif
}

__WN_INLINE WN_VOID WNCriticalSectionEnter(WN_CRITICAL_SECTION_T* _criticalSection) {
    #ifdef _WN_LINUX
        const WN_THREAD_ID_T thread = WNThreadID();
    #endif

    WN_RELEASE_ASSERT(_criticalSection != WN_NULL);

    #ifdef _WN_WINDOWS
        EnterCriticalSection(_criticalSection);
    #elif defined _WN_LINUX
        if (WNInterlockedIncrement(&(_criticalSection->mCounter)) > 1) {
            if (thread != _criticalSection->mOwner) {
                WNSemaphoreWait(&(_criticalSection->mSemaphore));
            }
        }

        _criticalSection->mOwner = thread;
        _criticalSection->mRecursion++;
    #endif
}

__WN_INLINE WN_VOID WNCriticalSectionLeave(WN_CRITICAL_SECTION_T* _criticalSection) {
    #ifdef _WN_LINUX
        WN_UINT32 recursion;
        WN_ATOM_T result;
    #endif

    WN_RELEASE_ASSERT(_criticalSection != WN_NULL);

    #ifdef _WN_WINDOWS
        LeaveCriticalSection(_criticalSection);
    #elif defined _WN_LINUX
        recursion = --(_criticalSection->mRecursion);

        if (recursion == 0) {
            _criticalSection->mOwner = 0;
        }

        result = WNInterlockedDecrement(&(_criticalSection->mCounter));

        if (result > 0) {
            if (recursion == 0) {
                WNSemaphorePost(&(_criticalSection->mSemaphore));
            }
        }
    #endif
}

#ifdef _WN_CPLUSPLUS
    __WN_INLINE WNCriticalSection::WNCriticalSection() {
        WNCriticalSectionInitialize(&mCriticalSection);
    }

    __WN_INLINE WNCriticalSection::~WNCriticalSection() {
        WNCriticalSectionDestroy(&mCriticalSection);
    }

    __WN_INLINE WN_VOID WNCriticalSection::Enter() {
        WNCriticalSectionEnter(&mCriticalSection);
    }

    __WN_INLINE WN_BOOL WNCriticalSection::TryEnter() {
        WNCriticalSectionTryEnter(&mCriticalSection);
    }

    __WN_INLINE WN_VOID WNCriticalSection::Leave() {
        WNCriticalSectionLeave(&mCriticalSection);
    }

    __WN_INLINE WNCriticalSectionScopeLock::WNCriticalSectionScopeLock(WN_CRITICAL_SECTION_T* _criticalSection) {
        WN_RELEASE_ASSERT(_criticalSection != WN_NULL);

        mCriticalSection = _criticalSection;

        WNCriticalSectionEnter(mCriticalSection);
    }

    __WN_INLINE WNCriticalSectionScopeLock::WNCriticalSectionScopeLock(WNCriticalSection& _criticalSection) {
        mCriticalSection = &(_criticalSection.mCriticalSection);

        WNCriticalSectionEnter(mCriticalSection);
    }

    __WN_INLINE WNCriticalSectionScopeLock::~WNCriticalSectionScopeLock() {
        WNCriticalSectionLeave(mCriticalSection);
    }
#endif

#endif // __WN_CRITICAL_SECTION_INL__
